<?php

// +----------------------------------------------------------------------
// | 悟空信息技术有限公司
// +----------------------------------------------------------------------
// | Copyright (c)2016 http://www.wkidt.com, All rights reserved.
// +----------------------------------------------------------------------
// | Author: wkidt team LSQ <admin@wkidt.com> 2017/10/4 9:26
// +----------------------------------------------------------------------
// | Readme: 事件类
// +----------------------------------------------------------------------

namespace app\system\logic;

use think\App;
use think\Config;
use think\Loader;

class Event
{

    /**
     * 注册的配置文件
     *
     * @var array
     */
    protected static $files = [];

    /**
     * 默认配置项
     *
     * @var array
     */
    protected static $defaultConfig = [
        'class' => null,
        'method' => null,
        'priority' => 0,
    ];

    /**
     * 监听者信息
     *
     * @var array
     */
    protected static $listeners = null;

    /**
     * 事件名称
     *
     * @var string
     */
    protected $name = '';

    /**
     * 停止事件
     *
     * @var bool
     */
    protected $isStop = false;

    /**
     * 事件附加数据
     *
     * @var array
     */
    protected $data = [];

    /**
     * 实例
     *
     * @var array
     */
    protected static $instances = [];

    /**
     * 缓存文件路径
     *
     * @var string
     */
    protected static $cacheFilename = RUNTIME_PATH . 'event.lock';

    /**
     * 初始化
     *
     * Event constructor.
     * @param $name
     */
    public function __construct($name)
    {
        $this->name = $name;
    }


    /**
     * 获取事件名称
     *
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * 中止事件
     *
     */
    public function stop()
    {
        $this->isStop = true;
    }

    /**
     * 事件是否已中止
     *
     * @return bool
     */
    public function isStopped()
    {
        return $this->isStop;
    }

    /**
     * 设置数据
     *
     * @param $name
     * @param null $value
     */
    public function setData($name, $value = null)
    {
        if (is_array($name)) {
            $this->data = $name;
        } else {
            $this->data[$name] = $value;
        }
    }

    /**
     * 获取数据
     *
     * @param null $name
     * @return array|mixed|null
     */
    public function getData($name = null)
    {
        if (is_null($name)) {
            return $this->data;
        } else {
            return isset($this->data[$name]) ? $this->data[$name] : null;
        }
    }

    /**
     * 触发事件
     *
     * @param $eventName
     * @param $args
     * @return Event
     */
    public static function trigger($eventName, &...$args)
    {
        array_unshift($args, new self($eventName));
        return call_user_func_array([self::class, 'dispatch'], $args);
    }

    /**
     * 调度事件
     *
     * @param $event
     * @param $args
     * @return Event
     */
    public static function dispatch(Event $event, &...$args)
    {
        $listeners = self::getListeners($event->getName());
        if (false != $listeners) {
            array_unshift($args, $event);
            foreach ($listeners as $key => &$listener) {
                call_user_func_array([self::getInstance($listener['class']), $listener['method']], $args);

                if ($event->isStopped()) break;
            }
        }
        return $event;
    }

    /**
     * 获取处理类的实例
     *
     * @param $class
     * @return mixed
     */
    protected static function getInstance($class)
    {
        if (!isset(self::$instances[$class])) {
            self::$instances[$class] = new $class();
        }
        return self::$instances[$class];
    }

    /**
     * 加载配置
     *
     */
    public static function load()
    {
        try {
            list($eventFilesKey, $handleFilesKey, $handleFiles, $listeners) = self::loadFromCache();

            if (!App::$debug || ($eventFilesKey == self::getEventFilesKey() && $handleFilesKey == self::getHandleFilesKey(unserialize($handleFiles)))) {
                self::$listeners = unserialize($listeners);
            } else {
                self::reCache();
            }
        } catch (\Exception $e) {
            self::reCache();
        }
    }

    /**
     * 从缓存加载
     *
     * @return array
     */
    protected static function loadFromCache()
    {
        return explode(PHP_EOL, file_get_contents(self::$cacheFilename), 4);
    }

    /**
     * 重新生成缓存文件
     *
     */
    protected static function reCache()
    {
        file_put_contents(self::$cacheFilename, implode(PHP_EOL, [
            self::getEventFilesKey(),
            self::getHandleFilesKey(),
            serialize(self::getHandleFiles()),
            serialize(self::getListeners())
        ]));
    }

    /**
     * 获取处理文件的指纹
     *
     * @param $handleFiles
     * @return null|string
     */
    protected static function getHandleFilesKey($handleFiles = null)
    {
        static $key = null;
        if (is_null($key)) {
            $filesInfo = '';
            $handleFiles = is_null($handleFiles) ? self::getHandleFiles() : $handleFiles;
            foreach ($handleFiles as $file) {
                $filesInfo .= $file . filemtime($file);
            }
            $key = md5($filesInfo);
        }
        return $key;
    }

    /**
     * 处理类文件列表
     *
     * @return array
     */
    protected static function getHandleFiles()
    {
        static $handleClassFiles = null;
        if (is_null($handleClassFiles)) {
            $handleClasses = [];
            $handleClassFiles = [];
            foreach (self::getAllEventFiles() as $file) {
                $eventConfig = include $file;
                foreach ($eventConfig as $k => $v) {
                    $class = is_numeric($k) ? $v : $k;
                    if (!in_array($class, $handleClasses)) {
                        $reflection = new \ReflectionClass($class);
                        $handleClassFiles[] = $reflection->getFileName();

                        $handleClasses[] = $class;
                    }
                }
            }
        }
        return $handleClassFiles;
    }

    /**
     * 获取事件配置的指纹
     *
     * @return null|string
     */
    protected static function getEventFilesKey()
    {
        static $key = null;
        if (is_null($key)) {
            $filesInfo = null;
            foreach (self::getAllEventFiles() as $file) {
                $filesInfo .= $file . filemtime($file);
            }
            $key = md5($filesInfo);
        }
        return $key;
    }

    /**
     * 添加事件监听
     *
     * @param $class
     * @param $options
     */
    public static function addListeners($class, $options)
    {
        if (class_exists($class)) {
            $reflection = new \ReflectionClass($class);
            $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);

            // 分析需要监听的事件项
            $items = [];
            if (is_string($options) && true !== $options) {
                $items = explode(',', $options);
            } elseif (is_array($options)) {
                $items = $options;
            }

            if (true === $options || in_array('*', $items)) {
                /**
                 *
                 * @var $method \ReflectionMethod
                 */
                foreach ($methods as $method) {
                    $items[] = $method->getName();
                }
            }

            // 处理监听关系
            $added = ['*'];
            foreach ($items as $k => $v) {
                if (false !== $options = self::parseOptions($class, $v)) {
                    if ($reflection->hasMethod($options['method']) && !in_array($options['method'], $added)) {
                        $eventName = is_numeric($k) ? Loader::parseName($options['method']) : $k;

                        if (isset(self::$listeners[$eventName])) {
                            self::$listeners[$eventName][] = $options;

                            // 重新排序
                            uasort(self::$listeners[$eventName], function ($a, $b) {
                                if ($a == $b) {
                                    return 0;
                                } elseif ($a > $b) {
                                    return -1;
                                } else {
                                    return 1;
                                }
                            });
                        } else {
                            self::$listeners[$eventName] = [$options];
                        }

                        $added[] = $options['method'];
                    }
                }
            }
        }
    }

    /**
     * 获取听众信息
     *
     * @param $name
     * @return array
     */
    protected static function getListeners($name = null)
    {
        if (is_null(self::$listeners)) {
            foreach (self::getAllEventFiles() as $file) {
                $eventConfig = include $file;
                foreach ($eventConfig as $key => $val) {
                    // 类名
                    if (is_numeric($key)) {
                        $class = $val;
                        $val = true;
                    } else {
                        $class = $key;
                    }
                    self::addListeners($class, $val);
                }
            }
        }

        if (is_null($name)) {
            return self::$listeners;
        } else {
            return isset(self::$listeners[$name]) ? self::$listeners[$name] : null;
        }
    }

    /**
     * 解析一项配置
     *
     * @param $class
     * @param $options
     * @return array|false
     */
    protected static function parseOptions($class, $options)
    {
        if (is_string($options)) {
            if (false === strpos($options, '@')) {
                $options = ['method' => $options];
            } else {
                list($method, $priority) = explode('@', $options, 2);
                $options = ['method' => $method, 'priority' => intval($priority)];
            }
        }
        return $options = array_merge(self::$defaultConfig, ['class' => $class], $options);
    }

    /**
     * 获取所有资源配置文件
     *
     * @return array
     */
    protected static function getAllEventFiles()
    {
        return array_merge(self::$files, (array)Config::get('event_file'), self::getModuleEventFiles());
    }

    /**
     * 注册资源配置文件
     *
     * @param $filename
     * @return bool
     */
    public static function file($filename)
    {
        if (file_exists($filename)) {
            self::$files[] = $filename;
            return true;
        } else {
            return false;
        }
    }

    /**
     * 获取所有模块事件配置文件
     *
     * @return array
     */
    protected static function getModuleEventFiles()
    {
        static $files = [];
        if (false == $files) {
            $dirs = scandir(APP_PATH);
            foreach ($dirs as $dir) {
                $filename = APP_PATH . $dir . DS . 'event.php';
                if (is_dir(APP_PATH . $dir) && file_exists($filename)) {
                    $files[] = $filename;
                }
            }
        }
        return $files;
    }
}
